/*
 * Copyright (c) 2012 swing-on-fire Team
 * 
 * This file is part of Swing-On-Fire (http://code.google.com/p/swing-on-fire), licensed under the terms of the MIT
 * License (MIT).
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
 * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
 * Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package com.google.code.swingonfire.graphic;

import java.awt.geom.AffineTransform;
import java.awt.geom.Path2D;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;

public class Path2DHelper
{

    private final double[] coords = new double[6];
    private final Path2D path;
    private final AffineTransform affineTransform;
    private final double divider;

    private Double flatness;
    private Double length;

    public Path2DHelper(Path2D path, AffineTransform affineTransform, double divider)
    {
        super();

        this.path = path;
        this.affineTransform = affineTransform;
        this.divider = divider;
    }

    public double getFlatness()
    {
        if (flatness == null)
        {
            flatness = Double.valueOf(calculateFlatness());
        }

        return flatness.doubleValue();
    }

    public double getLength()
    {
        if (length == null)
        {
            length = Double.valueOf(calculateLength());
        }

        return length;
    }

    public Point2D getPointByNormalizedDistance(double normalizedDistance)
    {
        return getPointByDistance(normalizedDistance * getLength());
    }

    public Point2D getPointByDistance(double distance)
    {
        double length = 0;
        Point2D position = new Point2D.Double();
        Point2D initial = null;
        PathIterator iterator = path.getPathIterator(affineTransform, getFlatness());
        double vx = 0;
        double vy = 0;
        double currentLength = Double.NaN;
        double lastX = Double.NaN;
        double lastY = Double.NaN;
        double lastLength = Double.NaN;

        while (!iterator.isDone())
        {
            lastX = position.getX();
            lastY = position.getY();
            lastLength = length;

            switch (iterator.currentSegment(coords))
            {
                case PathIterator.SEG_CLOSE:
                    vx = initial.getX() - position.getX();
                    vy = initial.getY() - position.getY();
                    currentLength = Math.sqrt((vx * vx) + (vy * vy));
                    length += currentLength;
                    position.setLocation(initial);
                    break;

                case PathIterator.SEG_MOVETO:
                    position.setLocation(coords[0], coords[1]);

                    if (initial == null)
                    {
                        initial = new Point2D.Double(coords[0], coords[1]);
                    }
                    break;

                case PathIterator.SEG_LINETO:
                    vx = coords[0] - position.getX();
                    vy = coords[1] - position.getY();
                    currentLength = Math.sqrt((vx * vx) + (vy * vy));
                    length += currentLength;
                    position.setLocation(coords[0], coords[1]);
                    break;

                default:
                    throw new UnsupportedOperationException("Only SEG_CLOSE, SEG_MOVETO and SEG_LINETO are supported");
            }

            if (length > distance)
            {
                double normalizedDistance = (distance - lastLength) / currentLength;

                position.setLocation(lastX + (vx * normalizedDistance), lastY + (vy * normalizedDistance));

                return position;
            }

            iterator.next();
        }

        // distance > length
        double normalizedDistance = (distance - lastLength) / currentLength;

        System.out.println("!" + currentLength + ", " + normalizedDistance + ", " + vx + ", " + lastX);
        position.setLocation(lastX + (vx * normalizedDistance), lastY + (vy * normalizedDistance));

        return position;
    }

    private double calculateFlatness()
    {
        double flatness = Double.MAX_VALUE;
        Point2D position = new Point2D.Double();
        PathIterator iterator = path.getPathIterator(affineTransform);
        double vx, vy, length;

        while (!iterator.isDone())
        {
            switch (iterator.currentSegment(coords))
            {
                case PathIterator.SEG_CLOSE:
                    break;

                case PathIterator.SEG_MOVETO:
                    break;

                case PathIterator.SEG_LINETO:
                    vx = coords[0] - position.getX();
                    vy = coords[1] - position.getY();
                    length = Math.sqrt((vx * vx) + (vy * vy));
                    flatness = Math.min(flatness, length / divider);
                    position.setLocation(coords[0], coords[1]);
                    break;

                case PathIterator.SEG_QUADTO:
                    vx = coords[2] - position.getX();
                    vy = coords[3] - position.getY();
                    length = Math.sqrt((vx * vx) + (vy * vy));
                    flatness = Math.min(flatness, length / divider);
                    position.setLocation(coords[2], coords[3]);
                    break;

                case PathIterator.SEG_CUBICTO:
                    vx = coords[4] - position.getX();
                    vy = coords[5] - position.getY();
                    length = Math.sqrt((vx * vx) + (vy * vy));
                    flatness = Math.min(flatness, length / divider);
                    position.setLocation(coords[4], coords[5]);
                    break;
            }

            iterator.next();
        }

        return flatness;
    }

    private double calculateLength()
    {
        double length = 0;
        Point2D position = new Point2D.Double();
        Point2D initial = null;
        PathIterator iterator = path.getPathIterator(affineTransform, getFlatness());
        double vx, vy;

        while (!iterator.isDone())
        {
            switch (iterator.currentSegment(coords))
            {
                case PathIterator.SEG_CLOSE:
                    vx = initial.getX() - position.getX();
                    vy = initial.getY() - position.getY();
                    length += Math.sqrt((vx * vx) + (vy * vy));
                    position.setLocation(initial);
                    break;

                case PathIterator.SEG_MOVETO:
                    position.setLocation(coords[0], coords[1]);

                    if (initial == null)
                    {
                        initial = new Point2D.Double(coords[0], coords[1]);
                    }
                    break;

                case PathIterator.SEG_LINETO:
                    vx = coords[0] - position.getX();
                    vy = coords[1] - position.getY();
                    length += Math.sqrt((vx * vx) + (vy * vy));
                    position.setLocation(coords[0], coords[1]);
                    break;

                default:
                    throw new UnsupportedOperationException("Only SEG_CLOSE, SEG_MOVETO and SEG_LINETO are supported");
            }

            iterator.next();
        }

        return length;
    }

}
